Skip to content

feat: builder writes under write-grants (Write API demo slice) - #226

Open
volod-vana wants to merge 3 commits into
mainfrom
volod/write-api-demo-slice
Open

feat: builder writes under write-grants (Write API demo slice)#226
volod-vana wants to merge 3 commits into
mainfrom
volod/write-api-demo-slice

Conversation

@volod-vana

Copy link
Copy Markdown
Member

Builders can now write into a user's PS under a write-grant. The owner key never leaves the PS: the server keeps signing AddData as the owner, the builder authenticates with its own key plus a grant. Implements the Write API tech design, demo slice scope.

How it works:

  • a write-grant is a normal gateway grant whose scope entries carry a write: prefix (write:notes.entries, write:chatgpt.*). Read policy matches entries verbatim, so a write-grant never grants read and a read-grant never grants write. Zero gateway changes.
  • POST /v1/write/session: Web3Signed handshake carrying the grantId, replay-guarded, mints a short-lived bearer. Same shape as the feat(mcp): chatbot-first self-signing MCP session + x402 pay-per-read #212 MCP session.
  • POST /v1/data/:scope accepts that bearer. Every write re-checks the live grant (revoke wins immediately, not at token expiry) and verifies X-Vana-Write-Signature recovers to the session builder.
  • attribution is stamped inside the envelope under reserved $writtenBy (same idiom as $binary), so it rides the unchanged encrypt/upload/register path. A payload that brings its own $writtenBy is a 400, a session write without the signature header is a 401.
  • fees: WriteFeeVerifierPort seam, default free until the PRD settles who pays. Real delete is a separate workstream.

Everything downstream of the auth gate is the untouched owner ingest path. PS-Lite and all existing surfaces are unaffected: authorizeWrite is an optional port, anything that is not a write-session bearer falls through to the old behavior.

Open question before merge: the write: prefix vs a first-class permissions field on the grant. The prefix needs zero DP RPC / EIP-712 changes, which is why v1 uses it. Raised in #eng_protocol.

Testing:

npm run e2e:write-api   # self-contained demo, mock gateway, 10 checks
npm run test:e2e        # incl. tests/e2e/write-api.e2e.test.ts against a booted server
npm test                # 1021 unit tests

Write API demo slice, authorization layer over the existing ingest path:

- policy/data-write.ts: verifyDataWritePolicy mirrors the read policy but
  only honors write:-prefixed grant scope entries, so a read-grant never
  confers write and a write-grant never confers read. Includes a
  WriteFeeVerifierPort seam (default free; fee mechanics undecided).
- write/session.ts: write-session store + createWriteSession handshake,
  same shape as the self-signing MCP session (prove key control once via
  Web3Signed, short-lived bearer after). PS keeps signing AddData as the
  owner; the builder never holds the owner key.
- write/attribution.ts: per-write builder-signed payload proof
  (X-Vana-Write-Signature, Web3Signed over the request incl. bodyHash),
  stored with the record inside envelope data under the reserved
  $writtenBy key (same in-data marker idiom as $binary) so it travels
  the unchanged encrypt/upload/register path. On-chain shape untouched.
- api/index.ts: optional authorizeWrite on PersonalServerApiAuthPort;
  POST /v1/data/:scope stays owner-only unless the port supports write
  sessions. Builder writes are stamped with attribution and logged to the
  access log with action write.
- contracts/data.ts: ingest contracts accept the attribution and reject
  payloads that carry the reserved key.

Claude-Session: https://claude.ai/code/session_018omZb8Q4JpPf4DvSUJfePY
- routes/write-session.ts: POST /v1/write/session, a Web3Signed handshake
  (builder key + write-grant id in the proof) mints a short-lived bearer
  bound to {builder, grant}, with the same replay guard discipline as the
  MCP session route.
- api-auth.ts: authorizeWrite redeems write-session bearers on the ingest
  endpoint, re-running the write policy against the LIVE grant per write
  and verifying the X-Vana-Write-Signature attribution proof; any other
  credential falls through to the unchanged owner path.
- app.ts: one shared in-memory session store wired into both the handshake
  route and the data routes (overridable via AppDeps for persistence).

Claude-Session: https://claude.ai/code/session_018omZb8Q4JpPf4DvSUJfePY
- tests/e2e/write-api.e2e.test.ts: full slice against a booted server --
  handshake, delegated write with attribution, read-back denied under the
  write-grant, read-back served under a separate read-grant.
- tests/e2e/helpers/mock-gateway.ts: grants are now seedable (setGrant).
- scripts/e2e-write-api.ts (npm run e2e:write-api): in-process demo with
  an injected mock gateway; verifies the stored attribution signature
  recovers to the builder over the original body bytes.

Claude-Session: https://claude.ai/code/session_018omZb8Q4JpPf4DvSUJfePY
@github-actions github-actions Bot added dependencies Pull requests that update a dependency file core server labels Aug 22, 2026
@github-actions

Copy link
Copy Markdown

Codex Review

  • [P1] Browser delegated writes fail CORS preflight — app.ts does not allow the newly required X-Vana-Write-Signature header. Add it to allowHeaders and cover an OPTIONS preflight in tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core dependencies Pull requests that update a dependency file server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant